﻿namespace yab

open System.Reflection
open System
open System.Collections.Generic
open TypeHelper

module Build =
    type AType = { Type : Type; Name : string } 

    let returning t f = f(t); t 
    let andApply f t = returning t f
    let log t = returning t (fun t -> printfn "Creating %s" <| t.ToString())
    
    let getWriteableProperties t = t.GetType().GetProperties() |> Array.filter (fun p -> p.CanWrite)

    let invokeWith f (aConstructor:ConstructorInfo) = f(aConstructor) |> aConstructor.Invoke
    
    let (|AString|AnInt|ADouble|ABoolean|ADateTime|AClass|UnknownType|) (propertyType:Type) =
        if      propertyType |> is<String>              then AString
        elif    propertyType |> isNullable<Int32>       then AnInt
        elif    propertyType |> isNullable<Double>      then ADouble
        elif    propertyType |> isNullable<Boolean>     then ABoolean
        elif    propertyType |> isNullable<DateTime>    then ADateTime
        elif    propertyType.IsClass                    then AClass
        else                                                 UnknownType
    
    let getParameters (methodBase:MethodBase) = methodBase.GetParameters()
    
    let rec valueFor (property:AType) : obj = 
        match property.Type with
        | AString           -> box property.Name
        | AnInt             -> box 0
        | ADouble           -> box 0.0
        | ABoolean          -> box false
        | ADateTime         -> box DateTime.MinValue
        | AClass            -> box <| build property.Type
        | _                 -> box <| new obj()                
    and build = getShortestConstructor >>
                invokeWith (getParameters >> Array.map (fun p -> valueFor { Type = p.ParameterType; Name = p.Name })) >>
                andApply (fun t -> getWriteableProperties t |> Array.iter(setValueOn t))
    and setValueOn t p = p.SetValue(t, valueFor {Type = p.PropertyType; Name = p.Name}, null)
                   
    type Builder<'a>() =
        let withs =new List<Action<'a>>()
    
        member this.With(f) =
            returning this (fun _ ->
                withs.Add(f))
    
        member this.Build() = 
            returning (build typeof<'a> :?> 'a) (fun t -> 
                Seq.iter  (fun (s:Action<'a>) -> s.Invoke(t)) withs)
        
    let A<'a>  = Builder<'a>()